package com.vinoxm.rss.handler.validate;

import com.vinoxm.common.base.exception.ValidationException;
import com.vinoxm.common.utils.CollectionUtils;
import com.vinoxm.common.utils.StringUtils;
import com.vinoxm.rss.handler.validate.annotation.ValidateColumn;
import com.vinoxm.rss.handler.validate.annotation.ValidateRule;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Log4j2
public class ControllerParamsValidator {

    private static final HashMap<String, Rule> validatorMap = new HashMap<>();

    private static final ThreadLocal<Map<String, Method>> localParamsMap = new ThreadLocal<>();

    private static void setLocalParamsMap(Map<String, Method> paramsMap) {
        localParamsMap.set(paramsMap);
    }

    public static Map<String, Method> getParamsMap() {
        return localParamsMap.get();
    }

    private static void addValidator(Class<?> clazz) {
        String clazzName = clazz.getName();
        log.info("[Validate params] add validator " + clazzName);
        HashMap<String, String> paramsMap = new HashMap<>();
        Set<String> uniqueSet = new HashSet<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(ValidateColumn.class)) {
                ValidateColumn annotation = field.getDeclaredAnnotation(ValidateColumn.class);
                String fieldName = field.getName();
                String value = annotation.value();
                boolean unique = annotation.unique();
                paramsMap.put(fieldName, StringUtils.isOrEmpty(value, ""));
                if (unique) uniqueSet.add(fieldName);
            }
        }
        ValidateRule ruleAnnotation = clazz.getDeclaredAnnotation(ValidateRule.class);
        Rule rule;
        if (null != ruleAnnotation) {
            rule = new Rule(Math.max(ruleAnnotation.min(), uniqueSet.size()), Math.max(-1, ruleAnnotation.max()), uniqueSet, paramsMap);
        } else rule = new Rule(uniqueSet.size(), -1, uniqueSet, paramsMap);
        validatorMap.put(clazzName, rule);

    }

    public static void validate(Class<?> clazz, Map<String, ?> params) {
        String clazzName = clazz.getName();
        if (!validatorMap.containsKey(clazzName)) {
            addValidator(clazz);
        }
        Rule rule = validatorMap.get(clazzName);
        int size = params.size();
        if (size < rule.getMin() || (rule.getMax() > -1 && size > rule.getMax())) throw new ValidationException("Error params quantity!");
        Set<String> uniqueSet = rule.getUniqueSet();
        if (CollectionUtils.some(uniqueSet, item -> !params.containsKey(item))) throw new ValidationException("Error params unique!");
        Map<String, String> columnMap = rule.getColumnMap();
        Map<String, Method> result = params.keySet().stream().collect(Collectors.toMap(h -> {
            if (!columnMap.containsKey(h)) throw new ValidationException(String.format("Error params: %s!", h));
            return StringUtils.isOrEmpty(columnMap.get(h), h);
        }, h -> {
            String methodName = StringUtils.humpWorkGet(h);
            Method method = null;
            try {
                method = clazz.getDeclaredMethod(methodName);
            } catch (NoSuchMethodException e) {
                throw new ValidationException("Error params append!");
            }
            return method;
        }));
        setLocalParamsMap(result);
    }

    public static class Rule {
        private final static int INIT_MIN = -1;
        private final static int INIT_MAX = -1;

        @Setter
        @Getter
        private int min;
        @Setter
        @Getter
        private int max;
        @Setter
        @Getter
        private Set<String> uniqueSet;
        @Setter
        @Getter
        private Map<String, String> columnMap;

        private Rule() {
        }

        private Rule(Integer min, Integer max, Set<String> uniqueSet, Map<String, String> columnMap) {
            this.min = null != min ? min : INIT_MIN;
            this.max = null != max ? max : INIT_MAX;
            this.uniqueSet = null != uniqueSet ? uniqueSet : Collections.emptySet();
            this.columnMap = null != columnMap ? columnMap : Collections.emptyMap();
        }

        private Rule(Integer min, Integer max, String[] uniqueArray, Map<String, String> columnMap) {
            this.min = null != min ? min : INIT_MIN;
            this.max = null != max ? max : INIT_MAX;
            this.uniqueSet = null != uniqueArray && uniqueArray.length > 0 ? new HashSet<>(Arrays.asList(uniqueArray)) : Collections.emptySet();
            this.columnMap = null != columnMap ? columnMap : Collections.emptyMap();
        }

        private Rule(Set<String> uniqueSet, Map<String, String> columnMap) {
            this.min = INIT_MIN;
            this.max = INIT_MAX;
            this.uniqueSet = null != uniqueSet ? uniqueSet : Collections.emptySet();
            this.columnMap = null != columnMap ? columnMap : Collections.emptyMap();
        }

        private Rule(String[] uniqueArray, Map<String, String> columnMap) {
            this.min = INIT_MIN;
            this.max = INIT_MAX;
            this.uniqueSet = null != uniqueArray && uniqueArray.length > 0 ? new HashSet<>(Arrays.asList(uniqueArray)) : Collections.emptySet();
            this.columnMap = null != columnMap ? columnMap : Collections.emptyMap();
        }
    }
}
